package cn.xerllent.wxtp.rest;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import cn.hutool.core.util.ObjectUtil;
import cn.xerllent.common.annotation.AnonymousAccess;
import cn.xerllent.common.annotation.Log;
import cn.xerllent.common.entity.vo.ResultData;
import cn.xerllent.common.exception.BadRequestException;
import cn.xerllent.common.utils.SecurityUtils;
import cn.xerllent.security.oauth2.WxTpAuthenticationToken;
import cn.xerllent.security.security.TokenUtil;
import cn.xerllent.security.security.vo.JwtUser;
import cn.xerllent.security.service.OnlineUserService;
import cn.xerllent.wxtp.config.WxCpTpConfiguration;
import cn.xerllent.wxtp.entity.WxAdmin;
import cn.xerllent.wxtp.entity.WxCorp;
import cn.xerllent.wxtp.entity.WxUser;
import cn.xerllent.wxtp.service.WxAdminService;
import cn.xerllent.wxtp.service.WxCorpService;
import cn.xerllent.wxtp.service.WxDepartmentService;
import cn.xerllent.wxtp.service.WxUserService;
import io.swagger.annotations.ApiOperation;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.impl.WxCpServiceOnTpImpl;
import me.chanjar.weixin.cp.bean.WxCpAgentJsapiSignature;
import me.chanjar.weixin.cp.bean.WxCpTpPermanentCodeInfo;
import me.chanjar.weixin.cp.bean.WxCpTpUserDetail;
import me.chanjar.weixin.cp.bean.WxCpTpUserInfo;
import me.chanjar.weixin.cp.bean.WxTpLoginInfo;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

/**
 * 企业微信应用通用接口
 */
@Slf4j
@RestController
@RequestMapping("/wxtp/{suiteName}/api")
public class SuiteController {

	@Value("${single.login}")
	private Boolean singleLogin;

	private final WxUserService wxUserService;
	private final OnlineUserService onlineUserService;
	private final WxCorpService wxCorpService;
	private final WxAdminService wxAdminService;
	private final WxDepartmentService wxDepartmentService;
	private final TokenUtil tokenUtil;

	public SuiteController(WxUserService wxUserService, OnlineUserService onlineUserService, WxCorpService wxCorpService, WxAdminService wxAdminService,TokenUtil tokenUtil,WxDepartmentService wxDepartmentService) {
		super();
		this.wxUserService = wxUserService;
		this.onlineUserService = onlineUserService;
		this.wxCorpService = wxCorpService;
		this.wxAdminService = wxAdminService;
		this.wxDepartmentService = wxDepartmentService;
		this.tokenUtil = tokenUtil;
	}

	private String oauthUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_privateinfo&state=%s#wechat_redirect";
	private String qrOauthUrl = "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=%s&redirect_uri=%s&state=%s&usertype=member";

	/**
	 * 跳转授权链接
	 * @param suiteName 应用名称
	 * @param redirectUri getPreAuthUrl
	 * @param state  a-zA-Z0-9的参数值（不超过128个字节），用于第三方自行校验session，防止跨域攻击
	 * @param authType  授权类型：0 正式授权， 1 测试授权。
	 */
    @Log("扫码安装预授权码获取接口")
    @ApiOperation("扫码安装预授权码获取接口")
    @AnonymousAccess
	@GetMapping("/getpreauthurl")
	public ResponseEntity<Object> getPreAuthUrl(@PathVariable String suiteName,@RequestParam String redirectUri, @RequestParam(defaultValue="0") int authType, @RequestParam(defaultValue = "xercode") String state) throws Exception {
		final WxCpTpService tpService = WxCpTpConfiguration.getCpTpService(suiteName);
		return ResponseEntity.ok(ResultData.data(tpService.getPreAuthUrl(redirectUri, state, authType)));
	}
	
	/**
	 * 扫码安装回调设置授权码接口 
	 * */
    @Log("扫码安装回调设置授权码接口")
    @ApiOperation("扫码安装回调设置授权码接口")
    @AnonymousAccess
    @GetMapping("/setauthcode")
	public ResponseEntity<Object> setAuthCode(@PathVariable String suiteName,@RequestParam(name = "auth_code") String authCode) throws Exception {
		final WxCpTpService wxCpTpService = WxCpTpConfiguration.getCpTpService(suiteName);
		WxCpTpPermanentCodeInfo permanentCode = wxCpTpService.getPermanentCodeInfo(authCode);
		
		String suiteId = wxCpTpService.getWxCpTpConfigStorage().getSuiteId();
		String corpId = permanentCode.getAuthCorpInfo().getCorpId();
		
		//先查是否存在
		WxCorp wxCorp = wxCorpService.getOne(new LambdaQueryWrapper<WxCorp>().eq(WxCorp::getCorpId, corpId).eq(WxCorp::getSuiteId, suiteId));
		if(wxCorp==null) {
			wxCorp = new WxCorp();
		}
		
		wxCorp.setCorpId(corpId)
		.setSuiteId(suiteId)
		.setAgentId(permanentCode.getAuthInfo().getAgents().get(0).getAgentId())
		.setCorpName(permanentCode.getAuthCorpInfo().getCorpName())
		.setCorpFullName(permanentCode.getAuthCorpInfo().getCorpFullName())
		.setPermanentCode(permanentCode.getPermanentCode())
		.setSubjectType(permanentCode.getAuthCorpInfo().getSubjectType())
		.setCreatetime(LocalDateTime.now())
		//.setVerifiedEndTime(new Date(permanentCode.getAuthCorpInfo().getVerifiedEndTime()).toInstant().atOffset(ZoneOffset.of("+8")).toLocalDateTime())
		.setWxpackage(permanentCode.toJson())
		.setStatus(1);
		
		wxCorpService.saveOrUpdate(wxCorp);
			
		return ResponseEntity.ok(ResultData.data(new WxCorp().setCorpId(wxCorp.getCorpId())));
	}
	
    @Log("获取oauth2用户认证跳转url")
    @ApiOperation("获取oauth2用户认证跳转url")
    @AnonymousAccess
	@GetMapping("/getoauthurl")
	public ResponseEntity<Object> getOauthUrl(@PathVariable String suiteName,@RequestParam String redirectUri, @RequestParam(defaultValue = "xercode") String state) throws Exception {
    	final WxCpTpService wxCpTpService = WxCpTpConfiguration.getCpTpService(suiteName);
    	String oauthUrl = String.format(this.oauthUrl, wxCpTpService.getWxCpTpConfigStorage().getSuiteId(), redirectUri, state);
		return ResponseEntity.ok(ResultData.data(oauthUrl));
	}

    @Log("获取oauth2用户扫码登录方式认证跳转url")
    @ApiOperation("获取oauth2用户扫码登录方式认证跳转url")
    @AnonymousAccess
    @GetMapping("/getqroauthurl")
    public ResponseEntity<Object> getQrOauthUrl(@PathVariable String suiteName,@RequestParam String redirectUri, @RequestParam(defaultValue = "xercode") String state) throws Exception {
    	final WxCpTpService wxCpTpService = WxCpTpConfiguration.getCpTpService(suiteName);
    	String oauthUrl = String.format(this.qrOauthUrl, wxCpTpService.getWxCpTpConfigStorage().getCorpId(), redirectUri, state);
    	return ResponseEntity.ok(ResultData.data(oauthUrl));
    }

    @Log("oauth2认证-根据授权码-用户登录 通过code换取用户信息")
    @ApiOperation("oauth2认证-根据授权码-用户登录 通过code换取用户信息")
    @AnonymousAccess
    @GetMapping("/socialLogin")
	public ResponseEntity<Object> socialLogin(@PathVariable String suiteName,
			@RequestParam String code,
			@RequestParam(defaultValue = "xercode") String state,
			HttpServletRequest request) throws WxErrorException {
		if (StringUtils.isBlank(code)) {
			throw new IllegalArgumentException("code is empty");
		}

		final WxCpTpService tpService = WxCpTpConfiguration.getCpTpService(suiteName);
//		WxSession  wxSession  =tpService.getSessionManager().getSession("sessionIdxxxx", true);
//		wxSession.setAttribute(suiteName, wxSession);

		WxCpTpUserInfo  wxCpTpUserInfo = tpService.getUserInfo3rd(code); //注意：与二维码扫码登录不是一个方法

		String corpId = wxCpTpUserInfo.getCorpId();
		String userId = wxCpTpUserInfo.getUserId();

		//1先看本地数据库是否有WxUser
		WxUser wxUser = wxUserService.getOne(new LambdaQueryWrapper<WxUser>().eq(WxUser::getCorpId, corpId).eq(WxUser::getUserId, userId));

		//2如果为空则从企业微信服务取,并存入本地
		if(ObjectUtil.isNull(wxUser)) {
			WxCpTpUserDetail wxCpTpUserDetail= tpService.getUserDetail3rd(wxCpTpUserInfo.getUserTicket());
			wxUser = new WxUser();
			wxUser.setUserId(wxCpTpUserDetail.getUserId())
			.setCorpId(wxCpTpUserDetail.getCorpId())
			.setAvatar(wxCpTpUserDetail.getAvatar())
			.setGender(wxCpTpUserDetail.getGender())
			.setName(wxCpTpUserDetail.getName()) //成员姓名，此字段从2019年12月30日起，对新创建第三方应用不再返回真实name，使用userid代替name返回，2020年6月30日起，对所有历史第三方应用不再返回，第三方页面需要通过通讯录展示组件来展示名字
			.setWxpackage(wxCpTpUserDetail.toJson())
			.setStatus(1);
			wxUserService.save(wxUser);
		}

		final JwtUser jwtUser = wxUserService.createJwtUser(wxUser);
		
		//3加载角色权限，检查是否是应用管理员,authorities=[admin]
		WxAdmin wxAdmin = new WxAdmin().setCorpId(corpId).setSuiteId(tpService.getWxCpTpConfigStorage().getSuiteId()).setUserId(userId);
		boolean isSuiteAdmin = wxAdminService.isSuiteAdmin(wxAdmin);
		if(isSuiteAdmin) {
			jwtUser.getAuthorities().add(new SimpleGrantedAuthority("admin")); //可能造成跨应用问题
		}

		//设置安全码
//		Authentication authentication = new WxTpAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
		SecurityContextHolder.getContext().setAuthentication(authentication);

		String token = tokenUtil.generateToken(jwtUser);

		// 保存在线信息
		onlineUserService.save(jwtUser, token, request);
		// 返回 token 与 用户信息
		Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
			//put("token", properties.getTokenStartWith() + token);
			put("token", token);  //客户端上传时head自带 properties.getTokenStartWith() = 'Bearer '
			put("user", jwtUser);
		}};

		if (singleLogin) {
			//踢掉之前已经登录的token
			onlineUserService.checkLoginOnUser(userId, token);
		}
		return ResponseEntity.ok(ResultData.data(authInfo));

	}

    @Log("oauth2认证-根据授权码-用户登录 通过code换取用户信息")
    @ApiOperation("oauth2认证-根据授权码-用户登录 通过code换取用户信息")
    @AnonymousAccess
    @GetMapping("/socialQrLogin")
	public ResponseEntity<Object> socialQrLogin(@PathVariable String suiteName,
			@RequestParam String authCode,
			@RequestParam(defaultValue = "xercode") String state,
			HttpServletRequest request) throws WxErrorException {
		if (StringUtils.isBlank(authCode)) {
			throw new IllegalArgumentException("authCode is empty");
		}

		final WxCpTpService tpService = WxCpTpConfiguration.getCpTpService(suiteName);
//		WxSession  wxSession  =tpService.getSessionManager().getSession("sessionIdxxxx", true);
//		wxSession.setAttribute(suiteName, wxSession);

		WxTpLoginInfo  wxTpUserInfo = tpService.getLoginInfo(authCode); //注意：与二维码扫码登录不是一个方法

		String corpId = wxTpUserInfo.getCorpInfo().getCorpId();
		String userId = wxTpUserInfo.getUserInfo().getUserId();

		//1先看本地数据库是否有WxUser
		WxUser wxUser = wxUserService.getOne(new LambdaQueryWrapper<WxUser>().eq(WxUser::getCorpId, corpId).eq(WxUser::getUserId, userId));

		//2如果为空则从企业微信服务取,并存入本地
		if(ObjectUtil.isNull(wxUser)) {
//			WxCpTpUserDetail wxCpTpUserDetail= tpService.getUserDetail3rd(wxTpUserInfo.getUserInfo().get);
			wxUser = new WxUser();
			wxUser.setUserId(userId)
			.setCorpId(corpId)
			.setAvatar(wxTpUserInfo.getUserInfo().getAvatar())
//			.setGender(wxCpTpUserDetail.getGender())
			.setName(wxTpUserInfo.getUserInfo().getName()) //成员姓名，此字段从2019年12月30日起，对新创建第三方应用不再返回真实name，使用userid代替name返回，2020年6月30日起，对所有历史第三方应用不再返回，第三方页面需要通过通讯录展示组件来展示名字
			.setWxpackage(WxCpGsonBuilder.create().toJson(wxTpUserInfo))
			.setStatus(1);
			wxUserService.save(wxUser);
		}

		final JwtUser jwtUser = wxUserService.createJwtUser(wxUser);
		
		//3加载角色权限，检查是否是应用管理员,authorities=[admin]
		WxAdmin wxAdmin = new WxAdmin().setCorpId(corpId).setSuiteId(tpService.getWxCpTpConfigStorage().getSuiteId()).setUserId(userId);
		boolean isSuiteAdmin = wxAdminService.isSuiteAdmin(wxAdmin);
		if(isSuiteAdmin) {
			jwtUser.getAuthorities().add(new SimpleGrantedAuthority("admin")); //可能造成跨应用问题
		}

		//设置安全码
//		Authentication authentication = new WxTpAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(jwtUser, null, jwtUser.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
		SecurityContextHolder.getContext().setAuthentication(authentication);

		String token = tokenUtil.generateToken(jwtUser);

		// 保存在线信息
		onlineUserService.save(jwtUser, token, request);
		// 返回 token 与 用户信息
		Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
			//put("token", properties.getTokenStartWith() + token);
			put("token", token);  //客户端上传时head自带 properties.getTokenStartWith() = 'Bearer '
			put("user", jwtUser);
		}};

		if (singleLogin) {
			//踢掉之前已经登录的token
			onlineUserService.checkLoginOnUser(userId, token);
		}
		return ResponseEntity.ok(ResultData.data(authInfo));

	}

    
    @ApiOperation("获取用户信息")
    @GetMapping(value = "/userInfo")
    public ResponseEntity<Object> getUserInfo(@PathVariable String suiteName) {
        try {
			JwtUser jwtUser = (JwtUser) wxUserService.loadUserByUsername(SecurityUtils.getUsername());
			
			//3加载角色权限，检查是否是应用管理员,authorities=[admin]
			final WxCpTpService tpService = WxCpTpConfiguration.getCpTpService(suiteName);
			WxAdmin wxAdmin = new WxAdmin().setCorpId(jwtUser.getCorpId()).setSuiteId(tpService.getWxCpTpConfigStorage().getSuiteId()).setUserId(jwtUser.getUserId());
			boolean isSuiteAdmin = wxAdminService.isSuiteAdmin(wxAdmin);
			if(isSuiteAdmin) {
				jwtUser.getAuthorities().add(new SimpleGrantedAuthority("admin")); //可能造成跨应用问题
			}
			
			Map<String, Object> result = new HashMap<String, Object>(1) {{
			    put("data", jwtUser);
			}};
			return ResponseEntity.ok(result);
		} catch (Exception e) {
			return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
		}
    }
    
    
    @ApiOperation("获取wx.Config参数信息")
    @GetMapping(value = "/jsSign")
    public ResponseEntity<Object> getJsSign(@PathVariable String suiteName, @RequestParam String url, @RequestParam String corpId) throws WxErrorException {
		if (StringUtils.isAnyBlank(url, corpId)) {
			throw new BadRequestException("请求参数非法，请核实!");
		}
		
		final WxCpTpService wxCpTpService = WxCpTpConfiguration.getCpTpService(suiteName);
//    	WxJsapiSignature sign = wxCpTpService.createAuthCorpJsApiTicketSignature(url, corpId);  //不知道用哪个？
//    	WxJsapiSignature sign = wxCpTpService.createSuiteJsApiTicketSignature(url, corpId);  //不知道用哪个？
		
		String suiteId = wxCpTpService.getWxCpTpConfigStorage().getSuiteId();
		
		//先查是否存在
		WxCorp wxCorp = wxCorpService.getOne(new LambdaQueryWrapper<WxCorp>().eq(WxCorp::getCorpId, corpId).eq(WxCorp::getSuiteId, suiteId));
		if(wxCorp==null) {
			throw new BadRequestException("记录不存在！WxCorp="+corpId+" suiteId="+suiteId);
		}
		
	    //第三方服务商service基础上构造授权企业service的代码api
	    WxCpService wxCpService = new WxCpServiceOnTpImpl(wxCpTpService);

	    val configStorage = new WxCpDefaultConfigImpl();
	    configStorage.setCorpId(corpId);
	    configStorage.setCorpSecret(wxCorp.getPermanentCode());//从数据库中查找出permanentCode
	    configStorage.setAgentId(wxCorp.getAgentId()); //生成jssdk sign时需要
	    
	    wxCpService.setWxCpConfigStorage(configStorage);
		
	    WxJsapiSignature  sign = wxCpService.createJsapiSignature(url);  //实际验证用这个
        return ResponseEntity.ok(sign);
    }

    
    @ApiOperation("获取wx.agentConfig参数信息")
    @GetMapping(value = "/jsAgentSign")
    public ResponseEntity<Object> getJsAgentSign(@PathVariable String suiteName, @RequestParam String url, @RequestParam String corpId) throws WxErrorException {
		if (StringUtils.isAnyBlank(url, corpId)) {
			throw new BadRequestException("请求参数非法，请核实!");
		}
		
		final WxCpTpService wxCpTpService = WxCpTpConfiguration.getCpTpService(suiteName);
    	
		String suiteId = wxCpTpService.getWxCpTpConfigStorage().getSuiteId();
		
		//先查是否存在
		WxCorp wxCorp = wxCorpService.getOne(new LambdaQueryWrapper<WxCorp>().eq(WxCorp::getCorpId, corpId).eq(WxCorp::getSuiteId, suiteId));
		if(wxCorp==null) {
			throw new BadRequestException("记录不存在！WxCorp="+corpId+" suiteId="+suiteId);
		}
		
	    //第三方服务商service基础上构造授权企业service的代码api
	    WxCpService wxCpService = new WxCpServiceOnTpImpl(wxCpTpService);

	    val configStorage = new WxCpDefaultConfigImpl();
	    configStorage.setCorpId(corpId);
	    configStorage.setCorpSecret(wxCorp.getPermanentCode());//从数据库中查找出permanentCode
	    configStorage.setAgentId(wxCorp.getAgentId()); //生成jssdk sign时需要
	    
	    wxCpService.setWxCpConfigStorage(configStorage);
		
	    WxCpAgentJsapiSignature  sign = wxCpService.createAgentJsapiSignature(url);
        return ResponseEntity.ok(sign);
    }
    
	/**
	 * 重新同步指定企业的通讯录数据,此接口只管理员私用，必要时测试同步接口及紧急同步数据
	 * @param suiteName 应用代码
     * @param corpId 指定要更新的企业id, corpId=all时指suiteName下所有企业,
     * @param type 指定同步类型 admins,departs,users(含部门)，type=all 时指所有类型
     * @throws Exception
     */
    @Log("重新同步指定企业的通讯录数据接口")
    @ApiOperation("重新同步指定企业的通讯录数据接口")
    @AnonymousAccess
	@GetMapping("/syncCorpData")
	public ResponseEntity<Object> syncCorpData(@PathVariable String suiteName,@RequestParam String corpId, @RequestParam String type) throws Exception {
		if (StringUtils.isAnyBlank(suiteName, corpId, type)) {
			throw new IllegalArgumentException("请求参数非法，请核实!");
		}
    	final WxCpTpService tpService = WxCpTpConfiguration.getCpTpService(suiteName);
    	
    	//企业集合
    	List<WxCorp> corps;
    	if(corpId.equals("all")) { //suiteName下所有企业
    		corps = wxCorpService.list(new LambdaQueryWrapper<WxCorp>().eq(WxCorp::getSuiteId, tpService.getWxCpTpConfigStorage().getSuiteId()));
    	} else { //单个
    		WxCorp wxCorp = wxCorpService.getOne(new LambdaQueryWrapper<WxCorp>().eq(WxCorp::getCorpId, corpId).last("LIMIT 1"));
    		if(wxCorp==null) {
    			throw new IllegalArgumentException("corpId参数非法，请核实!");
    		}
    		corps = new ArrayList<WxCorp>();
    		corps.add(wxCorp);
    	}
    	
    	for(WxCorp corp: corps) {
    		if(type.equals("all")) {
				//1. 同步应用管理员列表到本地数据库
				wxAdminService.syncAdminList(tpService, corp);

				//2. 同步企业部门及用户列表到本地数据库
				wxDepartmentService.syncDepartments(tpService, corp, true);
    		}else if(type.equals("admins")) {
				//1. 同步应用管理员列表到本地数据库
				wxAdminService.syncAdminList(tpService, corp);
    		}else if(type.equals("departs")) {
				//2. 同步企业部门及用户列表到本地数据库
				wxDepartmentService.syncDepartments(tpService, corp, false);
    		}else if(type.equals("users")) {
				//2. 同步企业部门及用户列表到本地数据库
				wxDepartmentService.syncDepartments(tpService, corp, true);
    		}
    	}
		
		return ResponseEntity.ok(ResultData.data("同步完毕!"));
	}

}
